package com.company.exp2.station.impl;

import com.company.exp2.util.C;
import com.company.exp2.context.Context;
import com.company.exp2.event.Event;
import com.company.exp2.factory.EventFactory;
import com.company.exp2.frame.Frame;
import com.company.exp2.station.Station;
import com.company.exp2.typeEnum.EventType;
import com.company.exp2.util.Recorder.Recordable;
import com.company.exp2.util.Recorder.Recorder;

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Random;

/**
 * @author dyy
 *
 * 默认的 Station 实现包含一个存储 Frame 的数据结构，
 * avoid 算法采用二元指数回退算法
 */
public class DefaultStation extends Station {

    private Event startTransmissionEvent;   //  指向自身创建的下一次传输事件，如果产生新的帧需要先取消旧的事件
    private boolean keepingAvoiding;        //  是否处于回避状态
    private int avoidFrequency;             //  回避积累次数
    private final Random random;            //  随机数生成器
    private final Queue<Frame> frames;      //  待传输的帧列表
    private boolean isWaitingACK;           //  是否在等待发送结果
    /**
     * 11/27
     * 由于超时时间发生后会直接触发二元指数回退算法，
     * 而这次二元指数回退算法产生的下一发送帧事件时间点很可能早于下一次 onChannelTransmittable 事件
     * 这时就会发生意料之外的帧碰撞
     *
     * 造成这一情况的原因是由于在 onChannelForbidden 事件和 onChannelTransmittable 事件之间超时
     * 而这一超时导致的回避操作倒计时并不会冻结
     * 这个成员存在的目的就是为了保证当超时事件发生时，应当能获取到应该冻结的时间长度
     */
    private long forbiddenTimeDuration;       //  冻结事件持续事件缓存
    private long forbiddenTimestamp;          //  冻结事件发生时间点
    private final Recordable recorder;              //  过程记录器

    {
        avoidFrequency = 1;
        random = new Random();
        frames = new ArrayDeque<>();
        forbiddenTimestamp = 0L;
        forbiddenTimeDuration = 0L;
        recorder = new Recorder();
    }

    @Override
    public void onChannelTransmittable(long timestamp) {
        if (frames.isEmpty()) return;
        if (isWaitingACK) return;
        if(!keepingAvoiding) {
            avoid(timestamp);
        }
    }

    @Override
    public void onChannelForbidden(long timestamp) {
        if (keepingAvoiding) {
            long newTimestamp = startTransmissionEvent.getTimestamp() + Context.currentContext
                    .getCurrentTransmittingFrame().getDuration() + C.DIFS;
            startTransmissionEvent.cancel();
            announceStartTransmissionEvent(newTimestamp);
        } else {
            forbiddenTimestamp = timestamp;
            forbiddenTimeDuration =
                    Context.currentContext.getCurrentTransmittingFrame().getDuration()
                    + C.DIFS;
        }
    }

    /**
     * 插入一个新的帧
     * 由 CREATE_FRAME 事件调用
     * 如果自己之前为空，则应当引起自身的二元指数回退
     * @param frame 需要添加到当前站点的帧
     */
    @Override
    public void insertFrame(Frame frame, long timestamp) {
        boolean isEmpty = frames.isEmpty();
        frames.add(frame);
        recorder.onPut(timestamp);
        if (isEmpty) {
            avoid(timestamp);
        }
    }

    /**
     * 释放一个帧的缓存
     */
    @Override
    public void dropFirstFrame() {
        frames.poll();
    }

    @Override
    public void onFrameStartTransmit(long timestamp) {
        Frame nextFrame = frames.peek();
        if (nextFrame != null) {
            keepingAvoiding = false;
            isWaitingACK = true;
            nextFrame.setStartTimestamp(timestamp);
            nextFrame.onAttemptToTransmit(timestamp);
        }
    }

    /**
     * 通过日志记录器记录一个帧传输成功，离开队列
     * @param timestamp 传输成功时间点
     */
    @Override
    public void onFrameTransmissionSucceed(long timestamp) {
        dropFirstFrame();
        recorder.onPop(timestamp);
        isWaitingACK = false;
    }

    @Override
    public void onFrameTransmissionFailed(long timestamp) {
        isWaitingACK = false;
        increaseAvoidFrequency();
        avoid(timestamp);
    }

    @Override
    public void setNo(int no) {
        super.setNo(no);
        recorder.setNo(no);
    }

    @Override
    public void log() {
        recorder.log();
    }

    private int pow(int avoidFrequency) {
        int result = 1;
        for (int j = 0 ; j < avoidFrequency ; j++) {
            result *= 2;
        }
        return result;
    }

    private void announceStartTransmissionEvent(long timestamp) {
        Event event = EventFactory.createInstance(EventType.FRAME_START_TRANSMIT, timestamp, this);
        Context.currentContext.getTimeLine().announceAnEvent(event);
        this.startTransmissionEvent = event;
    }

    private long generateAvoidTime() {
        int limit = pow(Math.min(avoidFrequency, 10));
        return random.nextInt(limit);
    }

    /**
     * 调用这个方法来产生回避时间过后的下一个传输事件
     * 如果信道被占用，意味着现在是冻结时间段，应当延后
     */
    private void avoid(long timestamp) {
        keepingAvoiding = true;
        if (!Context.currentContext.isVacant()) {
            timestamp += forbiddenTimeDuration - (timestamp - forbiddenTimestamp);
        }
        long avoidTime = timestamp + generateAvoidTime() * C.AVOID_TIME_UNIT;
        announceStartTransmissionEvent(avoidTime);
    }

    private void increaseAvoidFrequency() {
        avoidFrequency ++;
        if (avoidFrequency > 16) {
            System.out.println(" +++++++++++++++++ ");
            System.out.println(" 积累碰撞次数到达了16 ");
            System.out.println(" +++++++++++++++++ ");
        }
    }
}
